import axios, { Method } from "axios";
import { stringify } from "qs";
import { String, Union, List } from "ts-toolbelt"

const query = axios.create({
    withCredentials: true,
    baseURL: process.env.REACT_APP_BASEURL
})


export class Query<
    Url extends { [k in string]: string } = {},
    Data = any | string,
    Result = any
>{
    private readonly token = axios.CancelToken.source()
    private initialValue?: Result;
    public setInitialValue(data?: Result) {
        this.initialValue = data;
        return this;
    }
    public getInitialValue() {
        return this.initialValue;
    }
    constructor(
        private readonly url: string,
        private readonly method: Method,
        private readonly data?: Data
    ) { }
    public setData(data: Data, parser = true) {
        return new Query<Url, Data, Result>(
            this.url,
            this.method,
            parser ? stringify(data as any) as any : data
        ).setInitialValue(this.initialValue);
    }
    public params(params: Url) {
        const url = Object.keys(params).reduce((url, key) => {
            return url.replace(`:${key}`, params[key]).replace(`${key}=`, `${key}=${params[key]}`);
        }, this.url);
        let newUrl = url.split("?")[0];
        let queryParams = url.split("?")[1]?.split("&").filter(param => {
            const value = param.split("=")[1];
            return !(value === "undefined" || !value)
        }).join("&");
        if (!!queryParams) {
            newUrl += "?" + queryParams
        }
        return new Query<Url, Data, Result>(
            newUrl,
            this.method,
            this.data
        ).setInitialValue(this.initialValue);
    }
    public cancel(msg: string) {
        this.token.cancel(msg);
    }
    public async commit(callback: (err: any, data: Result) => void = () => null) {
        const entity = query({
            method: this.method,
            url: this.url,
            cancelToken: this.token.token,
            data: this.data
        });
        let err;
        const result: Result = await entity
            .then(({ data }) => {
                return data;
            }).catch((e) => {
                err = e;
                return null
            })
        if (!!callback) {
            callback(err, result);
        }
        if (!!err) {
            if (!!callback) return result;
            throw err;
        }
        return result;
    }
}
export const request = <Data, Result, T extends string = string>(url: T, method: Method = "GET") => {
    type Params = String.Split<String.Split<T, "?">[1], "&">
    type Query = Partial<Union.Merge<{
        [Element in Params[number]]: {
            [k in String.Split<Element, "=">[0]]: string | number
        }
    }[Params[number]]>>
    type QueryElements = List.Omit<String.Split<String.Split<T, "?">[0], ":">, 0>
    type QueryParams = Union.Merge<{
        [QueryElement in QueryElements[number]]: {
            [key in String.Split<QueryElement, "/">[0]]: string | number
        }
    }[QueryElements[number]]>
    return new Query<QueryParams & Query, Data, Result>(url, method);
}